﻿using System;
using UnityEngine;
using System.Collections.Generic;

namespace ProjectTW.Foliage
{
    public delegate void OnFoliageEnableChanged(FoliagePrototype changedPrototype, bool value);

    [Serializable]
    public sealed class FoliagePrototype
    {
        public const string Shader_Base_Name = "Foliage/FoliageShader_Basic";
        public const string Shader_Advanced_Name = "Foliage/FoliageShader_Advanced";

        public const int Max_Generation_Density = 6;

        public const float Size_Min_Value = 0.1f;
        public const float Size_Max_Value = 5.0f;

        private const int Mesh_Lods_Count = 1;

        public static Color DefaultHealthyColor = new Color(33f / 255, 129f / 255, 25f / 255, 1);
        public static Color DefaultDryColor = new Color(205f / 255, 188f / 255, 26f / 255, 1);

        public static event OnFoliageEnableChanged OnFoliageEnabledStateChangedEvent;

        private static GameObject s_FoliageGameObject;

        public static GameObject FoliageGameObject
        {
            get
            {
                if (s_FoliageGameObject == null)
                {
                    s_FoliageGameObject = Resources.Load<GameObject>("Foliage/DoubleSidedQuadCenterPivot");
                }

                return s_FoliageGameObject;
            }
        }

        public FoliageType FoliageType
        {
            get
            {
                return FoliagePrefab != null ? FoliageType.Prefab : FoliageType.Texture;
            }
        }

        [SerializeField]
        private GameObject m_FoliagePrefab;
        [SerializeField]
        private Texture2D m_FoliageTexture;

        [SerializeField]
        internal byte m_FoliageIdInternal;
        [SerializeField]
        private float m_Spread = 1f;
        [SerializeField]
        private float m_MinimumWidth = 1.5f;
        [SerializeField]
        private float m_MaximumWidth = 2f;
        [SerializeField]
        private float m_MinimumHeight = 1.5f;
        [SerializeField]
        private float m_MaximumHeight = 2f;
        [SerializeField]
        private bool m_ReceiveShadows = false;
        [SerializeField]
        private Color m_DryColor;
        [SerializeField]
        private Color m_HealthyColor;
        [SerializeField]
        private bool m_CastShadows = false;
        [SerializeField]
        private bool m_UseCustomFadeDistance = false;
        [SerializeField]
        private int m_FadeDistance = 100;
        [SerializeField]
        internal byte m_MaxGeneratedDensity = 5;
        [SerializeField]
        private bool m_UseInstancing = true;
        [SerializeField]
        private bool m_RotateNormals = false;
        [SerializeField]
        private bool m_Enabled = true;
        [SerializeField]
        private int m_RenderingLayer = 0;
        [SerializeField]
        private bool m_TouchBendingEnabled = true;
        [SerializeField]
        private float m_TouchBendingStrength = 0.97f;
        [SerializeField]
        private float m_CutOff = 0.3f;
        [SerializeField]
        private bool m_IsUseLods = false;
        [SerializeField]
        private List<FoliageLODLevel> m_Lods = null;
        [SerializeField]
        private FoliageMesh m_FoliageMesh;

        [SerializeField]
        private bool m_UseCustomWind = false;
        public FoliageWind CustomFoliageWind = new FoliageWind();
        
        [NonSerialized]
        private List<byte> m_DensitiesLods = null;

        public GameObject FoliagePrefab
        {
            get
            {
                return m_FoliagePrefab;
            }
            set
            {
                if (m_FoliagePrefab != value)
                {
                    m_FoliagePrefab = value;
                    GenerateInstantiatedMesh(HealthyColor, DryColor);
                    FoliageMeshManager.RegenerateQueueInstances();
                    FoliageMeshManager.GenerateFoliageMeshInstances();
                }
            }
        }

        public Texture2D FoliageTexture
        {
            get
            {
                return m_FoliageTexture;
            }
            set
            {
                if (m_FoliageTexture != value)
                {
                    m_FoliageTexture = value;
                    GenerateInstantiatedMesh(HealthyColor, DryColor);
                }
            }
        }

        public byte Id
        {
            get
            {
                return m_FoliageIdInternal;
            }
            private set
            {
                m_FoliageIdInternal = value;
            }
        }

        public float Spread
        {
            get
            {
                return m_Spread;
            }
            set
            {
                value = Mathf.Clamp(value, 0, 2);

                if (value != m_Spread)
                {
                    m_Spread = value;
                    UpdateManagerInformation();
                }
            }
        }

        public float MinimumWidth
        {
            get
            {
                return Mathf.Clamp(m_MinimumWidth, Size_Min_Value, m_MaximumWidth);
            }
            set
            {
                value = Mathf.Clamp(value, Size_Min_Value, m_MaximumWidth);

                if (value != m_MinimumWidth)
                {
                    m_MinimumWidth = value;
                    UpdateManagerInformation();
                }
            }
        }

        public float MaximumWidth
        {
            get
            {
                return Mathf.Clamp(m_MaximumWidth, m_MinimumWidth, Size_Max_Value);
            }
            set
            {
                value = Mathf.Clamp(value, m_MinimumWidth, Size_Max_Value);

                if (value != m_MaximumWidth)
                {
                    m_MaximumWidth = value;
                    UpdateManagerInformation();
                }
            }
        }

        public float MinimumHeight
        {
            get
            {
                return Mathf.Clamp(m_MinimumHeight, Size_Min_Value, m_MaximumHeight);
            }
            set
            {
                value = Mathf.Clamp(value, Size_Min_Value, m_MaximumHeight);

                if (value != m_MinimumHeight)
                {
                    m_MinimumHeight = value;
                    UpdateManagerInformation();
                }
            }
        }

        public float MaximumHeight
        {
            get
            {
                return Mathf.Clamp(m_MaximumHeight, m_MinimumHeight, Size_Max_Value);
            }
            set
            {
                value = Mathf.Clamp(value, m_MinimumHeight, Size_Max_Value);

                if (value != m_MaximumHeight)
                {
                    m_MaximumHeight = value;
                    UpdateManagerInformation();
                }
            }
        }

        public bool ReceiveShadows
        {
            get
            {
                return m_ReceiveShadows;
            }
            set
            {
                m_ReceiveShadows = value;
            }
        }

        public Color DryColor
        {
            get
            {
                return m_DryColor;
            }
            set
            {
                if (m_DryColor != value)
                {
                    m_DryColor = value;
                    UpdateManagerInformation();
                }
            }
        }

        public Color HealthyColor
        {
            get
            {
                return m_HealthyColor;
            }
            set
            {
                if (m_HealthyColor != value)
                {
                    m_HealthyColor = value;
                    UpdateManagerInformation();
                }
            }
        }

        public bool CastShadows
        {
            get
            {
                return m_CastShadows;
            }
            set
            {
                m_CastShadows = value;
            }
        }

        public bool UseCustomFadeDistance
        {
            get
            {
                return m_UseCustomFadeDistance;
            }
            set
            {
                if (m_UseCustomFadeDistance != value)
                {
                    m_UseCustomFadeDistance = value;
                    UpdateManagerInformation();

                    if (FoliageManager.Instance != null)
                    {
                        FoliageMeshManager.GenerateFoliageMeshInstances();
                        FoliageMeshManager.RegenerateQueueInstances(); // regenerate queue instances.
                    }
                }
            }
        }

        public int FadeDistance
        {
            get
            {
                return UseCustomFadeDistance || FoliageManager.Instance == null ? m_FadeDistance : FoliageManager.Instance.GlobalFadeDistance;
            }
            set
            {
                if (m_FadeDistance != value)
                {
                    m_FadeDistance = value;
                    bool _tempUseFadeDistance = UseCustomFadeDistance;
                    UseCustomFadeDistance = true;
                    UpdateManagerInformation();

                    // if FoliageCore_MainManager != null && custom fade distance didn't change.
                    if (FoliageManager.Instance != null && (_tempUseFadeDistance == UseCustomFadeDistance))
                    {
                        FoliageMeshManager.GenerateFoliageMeshInstances();
                        FoliageMeshManager.RegenerateQueueInstances(); // regenerate queue instances.
                    }
                }
            }
        }

        public int MaxGeneratedDensity
        {
            get
            {
                return Mathf.Clamp(m_MaxGeneratedDensity, 1, Max_Generation_Density);
            }
            set
            {
                value = Mathf.Clamp(value, 1, Max_Generation_Density);

                if (m_MaxGeneratedDensity != value)
                {
                    m_MaxGeneratedDensity = (byte) value;
                    m_DensitiesLods = null;
                    UpdateManagerInformation();

                    if (Enabled)
                    {
                        FoliageManager.GenerateFoliageMeshInstances(Id);
                        FoliageManager.RegenerateQueueInstances();
                    }
                }
            }
        }

        public bool UseInstancing
        {
            get
            {
                return m_UseInstancing;
            }
            set
            {
                if (m_UseInstancing != value)
                {
                    m_UseInstancing = value;
                    UpdateKeywords();

                    if (FoliageManager.Instance != null)
                    {
                        FoliageMeshManager.GenerateFoliageMeshInstances();
                        FoliageMeshManager.RegenerateQueueInstances();
                    }
                }
            }
        }

        public bool RotateNormals
        {
            get
            {
                return m_RotateNormals;
            }
            set
            {
                if (m_RotateNormals != value)
                {
                    m_RotateNormals = value;
                    UpdateManagerInformation();
                }
            }
        }

        public string Name
        {
            get
            {
                return FoliageType == FoliageType.Prefab ? FoliagePrefab == null ? "None" : FoliagePrefab.name : FoliageTexture == null ? "None" : FoliageTexture.name;
            }
        }

        public bool Enabled
        {
            get
            {
                return m_Enabled;
            }
            set
            {
                if (m_Enabled != value)
                {
                    m_Enabled = value;

                    if (OnFoliageEnabledStateChangedEvent != null)
                    {
                        OnFoliageEnabledStateChangedEvent(this, value);
                    }

#if UNITY_EDITOR
                    if (!Application.isPlaying)
                    {
                        UnityEditor.EditorUtility.SetDirty(FoliageDB.Instance);
                    }
#endif
                }
            }
        }

        public int MeshLodsCount
        {
            get
            {
                return Mathf.Clamp(Mesh_Lods_Count, 1, MaxGeneratedDensity);
            }
        }

        public int RenderingLayer
        {
            get
            {
                return m_RenderingLayer;
            }
            set
            {
                m_RenderingLayer = value;
            }
        }

        public float CutOff
        {
            get
            {
                return m_CutOff;
            }
            set
            {
                value = Mathf.Clamp(value, 0, 1);

                if (m_CutOff != value)
                {
                    m_CutOff = value;
                    FoliageMesh.Mat.SetFloat("_Cutoff", value);
                }
            }
        }

        public bool UseCustomWind
        {
            get
            {
                return m_UseCustomWind;
            }
            set
            {
                if (value != m_UseCustomWind)
                {
                    m_UseCustomWind = value;
                    FoliageDB.Instance.UpdateShaderWindSettings();
                }
            }
        }

        public bool IsUseLods
        {
            get
            {
                return m_IsUseLods;
            }
            set
            {
                if (m_IsUseLods != value)
                {
                    m_IsUseLods = value;
                    UpdateLODs();
                }
            }
        }

        public List<FoliageLODLevel> Lods
        {
            get
            {
                if (m_Lods == null)
                {
                    m_Lods = new List<FoliageLODLevel>(4);

                    m_Lods.Add(new FoliageLODLevel(20, 1f));
                    m_Lods.Add(new FoliageLODLevel(40, 0.6f));
                    m_Lods.Add(new FoliageLODLevel(60, 0.4f));
                    m_Lods.Add(new FoliageLODLevel(100, 0.2f));
                }

                return m_Lods;
            }
            set
            {
                m_Lods = value;
                bool changed = false;

                for (int i = 0; i < m_Lods.Count; i++)
                {
                    if (m_Lods[i].IsDirty)
                    {
                        changed = true;
                        m_Lods[i].IsDirty = false;
                    }
                }

                if (changed)
                {
                    UpdateLODs();
                }
            }
        }

        public int MaxFoliageCapability
        {
            get
            {
                return Mathf.FloorToInt(FoliageMeshInstance.Generation_Vertices_Max / FoliageMesh.VertexCount);
            }
        }

        public FoliageMesh FoliageMesh
        {
            get
            {
                return m_FoliageMesh;
            }
        }

        public List<byte> DensitiesLods
        {
            get
            {
                if (m_DensitiesLods == null)
                {
                    m_DensitiesLods = new List<byte>();
                    int meshLodsCount = Mathf.Clamp(this.MeshLodsCount, 1, MaxGeneratedDensity);

                    for (int i = 0; i < meshLodsCount; i++)
                    {
                        m_DensitiesLods.Add((byte) Mathf.FloorToInt((float) MaxGeneratedDensity / (i + 1)));
                    }
                }

                return m_DensitiesLods;
            }
        }

        private FoliagePrototype(Texture2D texture, GameObject prefab, float minWidth, float minHeight, float maxWidth, float maxHeight, float spread, int layer, byte id)
        {
            m_FoliagePrefab = prefab;
            m_FoliageTexture = texture;
            m_FoliageIdInternal = id;

            m_Spread = spread;
            m_RenderingLayer = layer;
            m_MinimumWidth = minWidth;
            m_MaximumWidth = maxWidth;
            m_MinimumHeight = minHeight;
            m_MaximumHeight = maxHeight;
        }

        /// <summary>
        /// Remove current material
        /// </summary>
        internal void DisposeOfCurrentMaterial()
        {
            if (m_FoliageMesh == null || m_FoliageMesh.Mat == null)
            {
                return;
            }

#if UNITY_EDITOR
            if (!Application.isPlaying)
            {
                UnityEditor.EditorUtility.SetDirty(FoliageDB.Instance);
            }

            if (FoliageMesh.Mat != null)
            {
                UnityEditor.AssetDatabase.DeleteAsset(UnityEditor.AssetDatabase.GetAssetPath(FoliageMesh.Mat));
            }

            UnityEditor.AssetDatabase.SaveAssets();
#endif
        }

        public bool EqualsToPrototype(DetailPrototype detail)
        {
            return detail.prototype == this.FoliagePrefab && detail.prototypeTexture == FoliageTexture;
        }

        public int GetRealPrototypeFadeDistance()
        {
            return m_FadeDistance;
        }

        internal void GenerateInstantiatedMesh(Color healthyColor, Color dryColor)
        {
            DisposeOfCurrentMaterial();
            GameObject instance;
            Vector3 offset;

            if (FoliageType == FoliageType.Prefab)
            {
                instance = FoliagePrefab;
                offset = Vector3.zero;
            }
            else
            {
                instance = FoliageGameObject;
                instance.GetComponent<MeshRenderer>().sharedMaterial.SetTexture("_MainTex", FoliageTexture);
                offset = new Vector3(0, 0.5f * instance.transform.localScale.y, 0); // assign offset.
            }

            m_FoliageMesh = new FoliageMesh(instance, Id, Name, offset);

            if (FoliageType == FoliageType.Prefab)
            {
                MaxGeneratedDensity = FoliageMesh.MeshInstancesLimiter_Optimization_Clamp;
            }

            this.HealthyColor = healthyColor;
            this.DryColor = dryColor;
            m_DensitiesLods = null;

            if (FoliageType == FoliageType.Prefab)
            {
                CopyLODsFromExistingLODGroup();
            }

            // Apply lods. & update keywords
            UpdateLODs();
            UpdateManagerInformation();
            UpdateKeywords();
        }

        /// <summary>
        /// Copy lods from existing lod group
        /// </summary>
        public void CopyLODsFromExistingLODGroup()
        {
            if (FoliagePrefab == null)
            {
                return;
            }

            LODGroup lodGroup = FoliagePrefab.GetComponent<LODGroup>();

            if (lodGroup == null)
            {
                return;
            }

            LOD[] lodGroupLODs = lodGroup.GetLODs();

            if (lodGroupLODs.Length > 4)
            {
                Debug.LogWarning("Cant have more than 4 lods, uNature will copy the first 4 lods instead.");
            }

            Lods.Clear();
            FoliageLODLevel lod;

            for (int i = 0; i < 4 && i < lodGroupLODs.Length; i++)
            {
                lod = FoliageLODLevel.CreateInstanceFromLODGroupLOD(lodGroupLODs[i], i);

                if (lod == null)
                {
                    continue;
                }

                Lods.Add(lod);
            }
        }

        public List<FoliageMeshData> GetLODsData()
        {
            List<FoliageMeshData> lodsData = new List<FoliageMeshData>();

            if (!IsUseLods || FoliageType == FoliageType.Texture)
            {
                lodsData.Add(new FoliageMeshData(FoliageMesh.Mesh));
                return lodsData;
            }

            FoliageLODLevel lod;
            Mesh currentMesh;
            FoliageMeshData lastMeshData = new FoliageMeshData();

            for (int i = 0; i < Lods.Count; i++)
            {
                lod = Lods[i];

                if (lod == null)
                {
                    continue;
                }

                currentMesh = lod.GetMeshLOD(i, this);

                if (currentMesh == null)
                {
                    lodsData.Add(lastMeshData); // if no mesh found, use the last mesh.
                }
                else
                {
                    lastMeshData = new FoliageMeshData(currentMesh);
                    lodsData.Add(lastMeshData);
                }
            }

            return lodsData;
        }

        public void UpdateLODs()
        {
            FoliageMesh.Mat.SetFloat("lods_Enabled", IsUseLods ? 1 : 0);
            FoliageLODLevel lod;

            for (int i = 0; i < FoliageLODLevel.LOD_MAX_AMOUNT; i++)
            {
                FoliageMesh.Mat.SetFloat("lod" + i + "_Distance", -1);
            }

            for (int i = 0; i < Lods.Count; i++)
            {
                lod = Lods[i];
                lod.LOD_Calculated_Distance = (FadeDistance * (float) lod.LOD_Coverage_Percentage) / 100f;
                FoliageMesh.Mat.SetFloat("lod" + i + "_Distance", lod.LOD_Calculated_Distance);
                FoliageMesh.Mat.SetFloat("lod" + i + "_Value", lod.LOD_Value_Multiplier);
                lod.IsDirty = false;
            }

            FoliageMeshManager.RegenerateQueueInstances();
        }

        public void ApplyWind()
        {
            FoliageWind targeted = UseCustomWind ? CustomFoliageWind : FoliageDB.Instance.GlobalFoliageWind;
            FoliageMesh.Mat.SetFloat("_WindSpeed", targeted.WindSpeed);
            FoliageMesh.Mat.SetFloat("_WindBending", targeted.WindBending);
        }

        public void ApplyColorMap(Texture2D map, Texture2D normalMap)
        {
            FoliageMesh.Mat.SetTexture("_WorldMap", normalMap);
        }
        
        public void ApplyDensityMap(Texture2D map)
        {
            FoliageMesh.Mat.SetTexture("_DensityMap", map);
        }

        public void UpdateKeywords()
        {
            if (UseInstancing)
            {
                FoliageMesh.Mat.EnableKeyword("INSTANCING_ON");
            }
            else
            {
                FoliageMesh.Mat.DisableKeyword("INSTANCING_ON");
            }

            FoliageMesh.Mat.enableInstancing = UseInstancing;
        }

        /// <summary>
        /// Update the global spread noise.
        /// </summary>
        public void UpdateManagerInformation()
        {
            if (FoliageManager.Instance == null)
            {
                return;
            }

            FoliageMesh.Mat.SetFloat("_DensityMultiplier", FoliageManager.Instance.Density);
            FoliageMesh.Mat.SetFloat("_NoiseMultiplier", Spread);
            FoliageMesh.Mat.SetFloat("_MinimumWidth", MinimumWidth);
            FoliageMesh.Mat.SetFloat("_MaximumWidth", MaximumWidth);
            FoliageMesh.Mat.SetFloat("_MinimumHeight", MinimumHeight);
            FoliageMesh.Mat.SetFloat("_MaximumHeight", MaximumHeight);
            FoliageMesh.Mat.SetFloat("_RotateNormals", RotateNormals ? 1 : 0);
            FoliageMesh.Mat.SetFloat("_FoliageAreaSize", FoliageManager.Foliage_Instance_Area_Size);
            FoliageMesh.Mat.SetFloat("FadeDistance", FadeDistance);
            FoliageMesh.Mat.SetFloat("MaxDensity", MaxGeneratedDensity);
            
            FoliageMesh.Mat.SetColor("_DryColor", m_DryColor * FoliageManager.Instance.FoliageGlobalTint);
            FoliageMesh.Mat.SetColor("_HealthyColor", m_HealthyColor * FoliageManager.Instance.FoliageGlobalTint);
            UpdateLODs();
        }

        /// <summary>
        /// Create a prototype.
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="prefab"></param>
        /// <param name="minSize"></param>
        /// <param name="maxSize"></param>
        /// <param name="spread"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public static FoliagePrototype CreatePrototype(Texture2D texture, GameObject prefab, float minWidth, float minHeight, float maxWidth, float maxHeight, float spread,
                                                       int layer, byte id, Color healthyColor, Color dryColor)
        {
            FoliagePrototype prototype = new FoliagePrototype(texture, prefab, minWidth, minHeight, maxWidth, maxHeight, spread, layer, id);

            FoliageDB.Instance.RegisterNewPrototype(prototype);
            prototype.GenerateInstantiatedMesh(healthyColor, dryColor);
            prototype.UpdateManagerInformation();
            FoliageManager.WarmUpDensityMaps();

            return prototype;
        }

        /// <summary>
        /// 根据距离获取模型的Index。
        /// </summary>
        /// <param name="distance"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public float GetLODMultiplier(float distance, ref int index)
        {
            if (!IsUseLods)
            {
                return 1;
            }

            FoliageLODLevel lod;

            for (int i = 0; i < Lods.Count; i++)
            {
                lod = Lods[i];

                if (distance <= lod.LOD_Calculated_Distance)
                {
                    index = i;

                    return lod.LOD_Value_Multiplier;
                }
            }

            index = 0;

            return 1;
        }
    }

    public enum FoliageType
    {
        Prefab,
        Texture
    }

    public enum ShaderType
    {
        NaN = 0,
        Basic = 1,
        Advanced = 2,
        Custom = 3
    }

    [System.Serializable]
    public class FoliageMesh
    {
        public const int Optimization_Mesh_Instances_Densities_Limiter = 6;
        public Mesh Mesh;
        public Material Mat;
        public Vector3 EulerAngles;

        [SerializeField]
        private Vector3 m_RendererScale;
        [SerializeField]
        private Vector3 m_WorldScale = Vector3.zero;
        [SerializeField]
        private Vector3 m_Scale = Vector3.one;
        [SerializeField]
        private Vector3 m_Offset;
        [SerializeField]
        private int m_MeshInstancesLimiter_Optimization_Clamp = FoliagePrototype.Max_Generation_Density;

        [NonSerialized]
        private int m_VertexCount;
        
        public Vector3 RendererScale
        {
            get
            {
                return m_RendererScale;
            }
        }

        public Vector3 WorldScale
        {
            get
            {
                if (m_WorldScale == Vector3.zero)
                {
                    m_WorldScale = RendererScale;
                    m_WorldScale.Scale(Scale);
                }

                return m_WorldScale;
            }
        }

        public Vector3 Scale
        {
            get
            {
                return m_Scale;
            }
        }

        public Vector3 Offset
        {
            get
            {
                return m_Offset;
            }
        }

        public int VertexCount
        {
            get
            {
                if (Mesh != null)
                {
                    if (Mesh.vertexCount != m_VertexCount)
                    {
                        m_VertexCount = Mesh.vertexCount;
                    }
                }

                return m_VertexCount;
            }
        }

        public ShaderType ShaderType
        {
            get
            {
                if (Mat != null)
                {
                    string shader = Mat.shader.name;
                    return shader == FoliagePrototype.Shader_Base_Name ? ShaderType.Basic :
                            shader == FoliagePrototype.Shader_Advanced_Name ? ShaderType.Advanced : ShaderType.Custom;
                }
                return ShaderType.NaN;
            }
        }

        public int MeshInstancesLimiter_Optimization_Clamp
        {
            get
            {
                return m_MeshInstancesLimiter_Optimization_Clamp;
            }
        }

        public FoliageMesh(GameObject go, int layer, string name, Vector3 offset)
        {
            MeshFilter[] filters = go.GetComponentsInChildren<MeshFilter>(true);

            if (filters.Length == 0)
            {
                return;
            }

            m_Offset = offset;
            go.transform.position = Vector3.zero + offset;
            EulerAngles = go.transform.eulerAngles;
            m_Scale = go.transform.localScale;
            CalculateRendererScale(go);
            MeshFilter filter = filters[0];
            Mesh = filter.sharedMesh;
            m_VertexCount = Mesh.vertexCount;
            Material matInstance = GameObject.Instantiate<Material>(go.GetComponentsInChildren<MeshRenderer>(true)[0].sharedMaterial);
            matInstance.name = "Foliage Material";
            Mat = matInstance;

            if (m_VertexCount > Optimization_Mesh_Instances_Densities_Limiter)
            {
                int differences = Mathf.Clamp((int) ((float) m_VertexCount / Optimization_Mesh_Instances_Densities_Limiter), 0, FoliagePrototype.Max_Generation_Density - 1);
                m_MeshInstancesLimiter_Optimization_Clamp = FoliagePrototype.Max_Generation_Density - differences;
            }

#if UNITY_EDITOR
            string materialsPath = UNSettings.ProjectPath + "Runtime/Resources/Foliage/Materials/";
            UnityEditor.AssetDatabase.CreateAsset(Mat, string.Format(materialsPath + "{0}_{1}_{2}.mat", Mat.name, name, layer));
            UnityEditor.AssetDatabase.SaveAssets();
#endif
        }

        private void CalculateRendererScale(GameObject obj)
        {
            Bounds bounds = new Bounds();
            MeshRenderer[] renderers = obj.GetComponentsInChildren<MeshRenderer>(true);

            for (int i = 0; i < renderers.Length; i++)
            {
                if (i == 0)
                {
                    bounds = renderers[i].bounds;

                    continue;
                }

                bounds.Encapsulate(renderers[i].bounds);
            }

            m_RendererScale = bounds.size;
        }
    }

    [Serializable]
    public class FoliageLODLevel
    {
        public static Color32[] lodGUIColors = new Color32[4]
        {
            new Color32(32, 178, 170, 255), new Color32(240, 128, 128, 255), new Color32(152, 251, 152, 255), new Color32(210, 105, 30, 255)
        };

        public static Color32[] lodGUIColors_overlay = new Color32[4]
        {
            new Color32(245, 245, 220, 125), new Color32(245, 245, 220, 125), new Color32(245, 245, 220, 125), new Color32(245, 245, 220, 125),
        };

        public const int LOD_MAX_DISTANCE = 500;
        public const int LOD_MAX_AMOUNT = 4;

        [SerializeField]
        private float lod_calculated_distance;
        [SerializeField]
        private float lod_value_multiplier;
        [SerializeField]
        private int lod_coverage_percentage;
        [SerializeField]
        private Mesh LOD_Mesh;
        
        internal bool IsDirty;

        public float LOD_Calculated_Distance
        {
            get
            {
                return lod_calculated_distance;
            }
            set
            {
                lod_calculated_distance = value;
            }
        }

        public float LOD_Value_Multiplier
        {
            get
            {
                return lod_value_multiplier;
            }
            set
            {
                if (lod_value_multiplier != value)
                {
                    lod_value_multiplier = value;
                    IsDirty = true;
                }
            }
        }

        public int LOD_Coverage_Percentage
        {
            get
            {
                return lod_coverage_percentage;
            }
            set
            {
                if (lod_coverage_percentage != value)
                {
                    lod_coverage_percentage = value;
                    IsDirty = true;
                }
            }
        }

        public Mesh GetMeshLOD(int lodId, FoliagePrototype prototype)
        {
            // first lod
            if (lodId == 0)
            {
                return prototype.FoliageMesh.Mesh;
            }

            return LOD_Mesh;
        }

        public void SetMeshLOD(Mesh mesh, FoliageLODLevel firstLOD)
        {
            if (LOD_Mesh != mesh)
            {
                if (firstLOD != null && firstLOD.LOD_Mesh != null && mesh != null && mesh.vertexCount > firstLOD.LOD_Mesh.vertexCount)
                {
                    Debug.LogError("Can't add lod mesh that has a higher vertex count than the original!!");

                    return;
                }

                LOD_Mesh = mesh;

                if (FoliageManager.Instance != null)
                {
                    FoliageMeshManager.GenerateFoliageMeshInstances();
                    FoliageMeshManager.RegenerateQueueInstances();
                }
            }
        }

        public FoliageLODLevel(int coverage, float valueMultiplier) : this(coverage, valueMultiplier, null)
        {
        }

        public FoliageLODLevel(int coverage, float valueMultiplier, Mesh lod_mesh)
        {
            LOD_Coverage_Percentage = coverage;
            LOD_Value_Multiplier = valueMultiplier;
            LOD_Mesh = lod_mesh;
        }

        public static FoliageLODLevel CreateInstanceFromLODGroupLOD(LOD lod, int lodIndex)
        {
            if (lod.renderers.Length == 0)
            {
                return null;
            }

            MeshFilter mFilter = lod.renderers[0].GetComponent<MeshFilter>();
            Mesh mesh = null;

            if (mFilter == null)
            {
                BillboardRenderer billboard = lod.renderers[0].GetComponent<BillboardRenderer>();

                if (billboard != null)
                {
                    mFilter = FoliagePrototype.FoliageGameObject.GetComponent<MeshFilter>();

                    if (mFilter == null)
                    {
                        return null;
                    }

                    mesh = mFilter.sharedMesh;
                }
            }
            else
            {
                if (mFilter.sharedMesh == null)
                {
                    return null;
                }

                mesh = mFilter.sharedMesh;
            }

            if (mesh == null)
            {
                return null;
            }

            int coverage = (int) ((1 - lod.screenRelativeTransitionHeight) * 100);
            float valueMultiplier = 1f / (lodIndex + 1); // add 1 to avoid division by 0.

            return new FoliageLODLevel(coverage, valueMultiplier, mesh);
        }
    }

    [Serializable]
    public struct FoliageMeshData
    {
        public Vector3[] Vertices;
        public int[] Triangles;
        public Vector2[] Uv;

        public int VerticesLength;
        public int NormalsLength;
        public int TrianglesLength;
        public int UvLength;

        public FoliageMeshData(Mesh mesh)
        {
            Vertices = mesh.vertices;
            Triangles = mesh.triangles;
            Uv = mesh.uv;
            VerticesLength = Vertices.Length;
            TrianglesLength = Triangles.Length;
            UvLength = Uv.Length;
            NormalsLength = mesh.normals.Length;
        }
    }
}
